﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.ComponentModel.Composition.Diagnostics;

namespace AxeFrog.Composition
{
	public class ContainerManager : IDisposable
	{
		private readonly CompositionContainer _container;
		private Factory _factory;

		public ContainerManager(CatalogBuilder cb)
			: this(cb.Build())
		{
		}

		public ContainerManager(CatalogManager cm)
			: this(cm.Catalog)
		{
		}

		public ContainerManager(ComposablePartCatalog cat)
		{
			_factory = new Factory(this);
			_container = new CompositionContainer(cat, true);
			_container.ComposeExportedValue(_factory);
		}

		public T Resolve<T>()
		{
			try
			{
				return _container.GetExportedValue<T>();
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public object Resolve(Type type)
		{
			try
			{
				var lazy = _container.GetExports(type, null, null).FirstOrDefault();
				if(lazy == null)
					throw new Exception("No available exports of type " + type);
				return lazy.Value;
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public object TryResolve(Type type)
		{
			try
			{
				var lazy = _container.GetExports(type, null, null).FirstOrDefault();
				return lazy == null ? null : lazy.Value;
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public T TryResolve<T>()
		{
			try
			{
				return _container.GetExportedValueOrDefault<T>();
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public T Resolve<T>(string contractName)
		{
			try
			{
				return _container.GetExportedValue<T>(contractName);
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public T TryResolve<T>(string contractName)
		{
			try
			{
				return _container.GetExportedValueOrDefault<T>(contractName);
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public IEnumerable<T> ResolveAll<T>()
		{
			try
			{
				return _container.GetExportedValues<T>();
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public IEnumerable<T> ResolveAll<T>(string contractName)
		{
			try
			{
				return _container.GetExportedValues<T>(contractName);
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public Lazy<T> Query<T>()
		{
			try
			{
				return _container.GetExport<T>();
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public Lazy<T, TMetadata> Query<T, TMetadata>()
		{
			try
			{
				return _container.GetExport<T, TMetadata>();
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public Lazy<T, TMetadata> Query<T, TMetadata>(string contractName)
		{
			try
			{
				return _container.GetExport<T, TMetadata>(contractName);
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public Lazy<T, TMetadata> Query<T, TMetadata>(Func<TMetadata, bool> metadataMatch)
		{
			try
			{
				return _container.GetExports<T, TMetadata>().Where(e => metadataMatch(e.Metadata)).FirstOrDefault();
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public IEnumerable<Lazy<T, TMetaData>> QueryAll<T, TMetaData>()
		{
			try
			{
				return _container.GetExports<T, TMetaData>();
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public IEnumerable<Lazy<object, object>> QueryAll(Type type, Type metadataViewType)
		{
			try
			{
				return _container.GetExports(type, metadataViewType, null);
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public void Release<T>(Lazy<T> export)
		{
			try
			{
				_container.ReleaseExport(export);
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		private Exception PreProcessException(Exception ex)
		{
			Console.WriteLine(ex.ToString());
			var cmex = new ContainerManagerException(ex, Dump());
#if DEBUG
			if(System.Diagnostics.Debugger.IsAttached)
				System.Diagnostics.Debugger.Break();
#endif
			return cmex;
		}

		public string Dump()
		{
			var ci = new CompositionInfo(_container.Catalog, _container);
			StringWriter writer = new StringWriter();
			CompositionInfoTextFormatter.Write(ci, writer);
			return writer.ToString();
		}

		public void Apply(object part)
		{
			try
			{
				CompositionBatch batch = new CompositionBatch();
				batch.AddPart(part);
				_container.Compose(batch);
			}
			catch(Exception ex)
			{
				throw PreProcessException(ex);
			}
		}

		public void Dispose()
		{
			_container.Dispose();
		}
	}

	[Serializable]
	public class ContainerManagerException : Exception
	{
		public ContainerManagerException()
		{
		}

		public ContainerManagerException(Exception ex, string logDump)
			: base(ex.Message, ex)
		{
			LogDump = logDump;
		}

		public string LogDump { get; set; }
	}
}
